/**
 * @file NetlistManager.cc
 * @author Xiaoze Lin (linxiaoze96@gmail.com)
 * @brief The implementation of the netlist manager class.
 * @version 0.1
 *
 * @copyright Copyright (c) 2024
 *
 */
#include "NetlistManager.hh"

namespace ifsim {

NetlistManager* NetlistManager::_netlistManager = nullptr;

NetlistManager::NetlistManager() : _splitTieNodes(false), _maxFanout(4), _dffBuffer(true)
{
  ;
}

NetlistManager::~NetlistManager() = default;

/**
 * @brief: Get the netlist manager instance, if not, create one.
 * @return NetlistManager*
 */
NetlistManager* NetlistManager::getOrCreateNetlistManager()
{
  if (_netlistManager == nullptr) {
    _netlistManager = new NetlistManager();
  }
  return _netlistManager;
}

/**
 * @brief: Destroy the netlist manager.
 *
 */
void NetlistManager::destroyNetlistManager()
{
  delete _netlistManager;
  _netlistManager = nullptr;
}

/**
 * @brief: Read the verilog file.
 * @param {char*} verilog_file
 * @return status
 */
bool NetlistManager::readVerilog(const char* verilog_file)
{
  inputCktFile(verilog_file);
  bool is_ok = _verilogReader.read(verilog_file);
  return is_ok;
}

/**
 * @brief: Construct standard cell netlist based on parsed verilog design.
 * @param {std::string} top_module_name
 * @param {AtpgLibrary} atpg_lib
 * @return none
 */
void NetlistManager::verilogToStdCellNetlist(std::string top_module_name, AtpgLibrary* atpg_lib)
{
  LOG_INFO << "convert verilog to std cell netlist start";
  // ieda::Stats stats;
  std::multimap<std::string, std::pair<int, int>> ref_node_port;  // map a output net name to a pair (node, port)

  // flattern modules
  auto* top_module = _verilogReader.flattenModule(top_module_name.c_str());

  auto& verilog_modules = _verilogReader.get_verilog_modules();
  _verilogModules = std::move(verilog_modules);
  auto& top_module_stmts = top_module->get_module_stmts();

  _atpgLib = atpg_lib;

  // lambda function, add one std node
  auto add_single_snode = [&ref_node_port](StdCellandIONode& snode, auto dcl_type, const auto* dcl_name, int& node_id) {
    StdCellandIONodeType type = STD_INPUT;
    if (VerilogDcl::DclType::kInput == dcl_type) {
      type = STD_INPUT;
      snode.setPortCnt(0, 1);  // in, out
      snode.resizeSucc(1);
      snode.portRefName(dcl_name, 0, false);
      auto node_port = std::make_pair(node_id, 0);
      ref_node_port.insert(std::pair<string, std::pair<int, int>>(dcl_name, node_port));
    } else if (VerilogDcl::DclType::kOutput == dcl_type) {
      type = STD_OUTPUT;
      snode.setPortCnt(1, 0);  // in, out
      snode.resizePred(1);
      snode.portRefName(dcl_name, 0, true);
    } else if (VerilogDcl::DclType::kInout == dcl_type) {
      type = STD_INOUT;
      snode.setPortCnt(1, 1);  // in, out
      snode.resizePred(1);
      snode.resizeSucc(1);
      snode.portRefName(dcl_name, 0, true);
      snode.portRefName(dcl_name, 0, false);
      auto node_port = std::make_pair(node_id, 0);
      ref_node_port.insert(std::pair<string, std::pair<int, int>>(dcl_name, node_port));
    }
    snode.setBasicInfo(node_id++, type, dcl_name, "");  // stdId, type, name, stdCellName
  };

  // lambda function, process dcl
  auto process_dcl_stmt = [&ref_node_port, add_single_snode](StdCellNetlist& slist, auto* dcl_stmt, int& node_id) {
    auto dcl_type = dcl_stmt->get_dcl_type();
    const auto* dcl_name = dcl_stmt->get_dcl_name();  // input/output/inout/wire name
    auto dcl_range = dcl_stmt->get_range();           // width

    if (VerilogDcl::DclType::kWire == dcl_type) {  // wire do not build std cell node
      return;
    } else if ((dcl_type != VerilogDcl::DclType::kInout) && (dcl_type != VerilogDcl::DclType::kInput)
               && (dcl_type != VerilogDcl::DclType::kOutput)) {
      LOG_INFO << "not support the declaration " << dcl_name;
    }

    if (!dcl_range) {
      StdCellandIONode snode;
      add_single_snode(snode, dcl_type, dcl_name, node_id);
      slist.addNode(snode);
    } else {
      auto width_range = *(dcl_range);
      for (int index = width_range.second; index <= width_range.first; ++index) {
        StdCellandIONode snode;
        const char* one_name = Str::printf("%s[%d]", dcl_name, index);
        add_single_snode(snode, dcl_type, one_name, node_id);
        slist.addNode(snode);
      }
    }
  };

  // process statements in flattened top module one by one
  int node_id = 0;
  for (auto& stmt : top_module_stmts) {
    if (stmt->isVerilogDclsStmt()) {
      auto* dcls_stmt = dynamic_cast<VerilogDcls*>(stmt.get());
      auto& dcls = dcls_stmt->get_verilog_dcls();
      for (auto& dcl_stmt : dcls) {
        process_dcl_stmt(_stdCellNetlist, dcl_stmt.get(), node_id);
      }
    } else if (stmt->isVerilogDclStmt()) {
      StdCellandIONode snode;
      auto* dcl_stmt = dynamic_cast<VerilogDcl*>(stmt.get());
      process_dcl_stmt(_stdCellNetlist, dcl_stmt, node_id);
    } else if (stmt->isModuleInstStmt()) {
      auto* inst_stmt = dynamic_cast<VerilogInst*>(stmt.get());    // instance statement
      const char* cell_name = inst_stmt->get_cell_name();          // standard cell model name
      auto& port_connections = inst_stmt->get_port_connections();  // instance connections
      const char* inst_name = inst_stmt->get_inst_name();          // instance name
      StdCellModel* scellp = atpg_lib->findCell(cell_name);        // cell model

      StdCellandIONode snode;
      snode.setBasicInfo(node_id++, STD_CELL, inst_name, cell_name);  // stdId, type, name, stdCellName
      snode.setPortCnt(scellp->cellInputNum(), scellp->cellOutputNum());
      snode.resizePred(scellp->cellInputNum());
      snode.resizeSucc(scellp->cellOutputNum());

      for (auto& port_connection : port_connections) {
        LOG_FATAL_IF(!port_connection) << "The inst " << inst_name << " at line " << inst_stmt->get_line() << " port connection is null ";
        auto cell_port_id = port_connection->get_port_id();  // port name
        auto* net_expr = port_connection->get_net_expr();
        auto cell_inputs = scellp->cellInputs();
        auto cell_outputs = scellp->cellOutputs();
        bool is_input = false;
        int port_id = 0;
        for (std::vector<string>::iterator iter = cell_inputs.begin(); iter != cell_inputs.end(); ++iter) {
          if (cell_port_id->getName() == *iter) {
            is_input = true;
            snode.portName(cell_port_id->getName(), port_id, is_input);
            if (net_expr) {
              auto net_id = net_expr->get_verilog_id();  // port connection name
              snode.portRefName(net_id->getName(), port_id, is_input);
            }
            break;
          }
          ++port_id;
        }
        if (is_input) {
          continue;
        }
        port_id = 0;
        for (std::vector<string>::iterator iter = cell_outputs.begin(); iter != cell_outputs.end(); ++iter) {
          if (cell_port_id->getName() == *iter) {
            snode.portName(cell_port_id->getName(), port_id, is_input);
            if (net_expr) {
              auto net_id = net_expr->get_verilog_id();  // port connection name
              snode.portRefName(net_id->getName(), port_id, is_input);
              auto node_port = std::make_pair(snode.stdId(), port_id);
              std::string port_connection_name = net_id->getName();
              std::string suffix = "\n";
              if (port_connection_name.ends_with(suffix)) {
                port_connection_name = port_connection_name.substr(0, port_connection_name.size() - 1);
              }
              ref_node_port.insert(std::pair<string, std::pair<int, int>>(port_connection_name, node_port));
            }
            break;
          }
          port_id++;
        }
      }
      _stdCellNetlist.addNode(snode);
    } else if (stmt->isModuleAssignStmt()) {  // assignment stmt
      auto* assign_stmt = dynamic_cast<VerilogAssign*>(stmt.get());
      auto* left_net_expr = assign_stmt->get_left_net_expr();
      auto* right_net_expr = assign_stmt->get_right_net_expr();
      std::string left_net_expr_id = left_net_expr->get_verilog_id()->getName();
      std::string right_net_expr_id = right_net_expr->get_verilog_id()->getName();
      StdCellandIONode snode;                                          // standard cell node
      snode.setBasicInfo(node_id++, STD_CELL, "ASSIGN_BUF", "BUFX1");  // stdId, type, name, stdCellName
      // StdCellandIONode snode;                                                           // standard cell node
      // snode.setBasicInfo(node_id++, STD_CELL, "ASSIGN_BUF", "sky130_fd_sc_hd__buf_1");  // stdId, type, name, stdCellName
      snode.setPortCnt(1, 1);
      snode.resizePred(1);
      snode.resizeSucc(1);
      snode.portName("A", 0, true);
      snode.portRefName(right_net_expr_id, 0, true);
      snode.portName("Y", 0, false);
      // snode.portName("X", 0, false);
      snode.portRefName(left_net_expr_id, 0, false);
      auto node_port = std::make_pair(snode.stdId(), 0);
      ref_node_port.insert(std::pair<string, std::pair<int, int>>(left_net_expr_id, node_port));
      _stdCellNetlist.addNode(snode);
    }
  }

  // complete preds and succs info
  int num_floating_net = 0;
  std::set<std::string> floating_net;
  ref_node_port.insert(std::pair<string, std::pair<int, int>>("1'b1", std::make_pair(tie1, 0)));
  ref_node_port.insert(std::pair<string, std::pair<int, int>>("1'b0", std::make_pair(tie0, 0)));
  ref_node_port.insert(std::pair<string, std::pair<int, int>>("1'bx", std::make_pair(tiex, 0)));
  for (int nid = 0; nid < _stdCellNetlist.stdCellandIONodeCnt(); ++nid) {
    StdCellandIONode snode = _stdCellNetlist.stdCellandIONode(nid);
    int in_port_cnt = snode.inPortCnt();
    for (int pid = 0; pid < in_port_cnt; ++pid) {
      std::string pname = snode.predRef(pid);  // pred port connection name
      std::string suffix = "\n";
      if (pname.ends_with(suffix)) {
        pname = pname.substr(0, pname.size() - 1);
      }
      auto pr = ref_node_port.equal_range(pname);
      if (pr.first == std::end(ref_node_port) || pr.first == pr.second) {  // not found port named pname
        // for [0:0] PI
        pr = ref_node_port.equal_range(pname + "[0]");
        if (pr.first == std::end(ref_node_port) || pr.first == pr.second) {
          // for [0:0] PO
          pr = ref_node_port.equal_range(pname.substr(0, pname.size() - 3));
          if (pr.first == std::end(ref_node_port) || pr.first == pr.second) {  // floating net
            if (floating_net.find(pname) == floating_net.end()) {
              ++num_floating_net;
              floating_net.insert(pname);
              // std::cout << "Floating net named " << pname << std::endl;
            }
            pr = ref_node_port.equal_range("1'bx");
          }
        }
      }
      for (auto iter = pr.first; iter != pr.second; ++iter) {
        auto node_port = iter->second;  // (nodeid, portid) pair
        int node_id = node_port.first;
        int port_id = node_port.second;
        if (nid == node_id && snode.type() != STD_CELL) {  // for inout not to record itself as pred and succ
          continue;
        }
        _stdCellNetlist.predIdPort(nid, pid, node_id, port_id);
        if ((node_id != tie1) && (node_id != tie0) && (node_id != tiex)) {
          _stdCellNetlist.succIdPort(nid, pid, node_id, port_id);
        }
      }
    }
  }
  if (num_floating_net) {
    std::cout << WarnColor << "There are " << num_floating_net << " floating nets." << NormalColor << std::endl;
  }

  // dump time message
  // double time_delta = stats.elapsedRunTime();
  // LOG_INFO << "verilog to std cell netlist time elapsed " << time_delta << "s";

  _stdCellNetlist.computeCircuitStatistics();
  // _stdCellNetlist.printCktStatistics();

  LOG_INFO << "convert verilog to std cell netlist end";
}

/**
 * @brief: Construct primitive netlist based on std cell netlist.
 * @return none
 */
void NetlistManager::stdCellNetlistToPrimitiveNetlist(AtpgLibrary* atpg_lib)
{
  LOG_INFO << "convert std cell netlist to primitive netlist start";
  _atpgLib = atpg_lib;
  ieda::Stats stats;
  _priNetlist.clear();

  int pri_cnt = 0;                                          // record Primitive node id
  auto& std_node_vec = _stdCellNetlist.stdCellandIONode();  // the vector of StdCellandIONode in StdCellNetlist

  vector<vector<int>> std_output_id_map(std_node_vec.size());  // to store all the output_id_map of std nodes

  // lambda function, in_id_map[scell] = snode
  auto map_input_position = [](vector<int>& in_id_map, StdCellandIONode& snode, StdCellModel* scellp) {
    for (int in_id = 0; in_id < snode.inPortCnt(); ++in_id) {
      int map_id = 0;
      while (snode.inPortName(map_id) != scellp->cellInputs(in_id)) {
        ++map_id;
        if (map_id > snode.inPortCnt()) {
          LOG_ERROR << "wrong when map output position";
        }
      }
      in_id_map[in_id] = map_id;
    }
  };

  // lambda function, out_id_map[scell] = snode
  auto map_output_position = [](vector<int>& out_id_map, StdCellandIONode& snode, StdCellModel* scellp) {
    for (int out_id = 0; out_id < snode.outPortCnt(); ++out_id) {
      int map_id = 0;
      while (snode.outPortName(map_id) != scellp->cellOutputs(out_id)) {
        ++map_id;
        if (map_id > snode.outPortCnt()) {
          LOG_ERROR << "wrong when map output position";
        }
      }
      out_id_map[out_id] = map_id;
    }
  };

  // lambda function, adjust output id in pir_in_std_cell
  // auto adjust_output_id = [](vector<int>& pid_in_std_cell, StdCellModel* scellp, int offset, vector<int>& out_id_map) {
  //   int first_out = pid_in_std_cell[offset];
  //   for (int out_id = 0; out_id < scellp->cellOutputNum(); out_id++) {
  //     pid_in_std_cell[offset + out_id] = first_out + out_id_map[out_id];
  //   }
  // };

  // lambda function, set pri id, std id, gate type, std instance name
  auto set_pnode_basic_info = [](auto& pnodep, int& pri_id, int std_id, auto type, auto std_inst_name) {
    pnodep->gid(pri_id++);
    pnodep->gidStd(std_id);
    pnodep->type(type);
    pnodep->stdCellInstName(std_inst_name);
  };

  // step 1, traverse std cell netlist, allocate Primitive node, assign node id and type
  for (auto& snode : std_node_vec) {
    StdCellModel* scellp;
    PrimitiveGate* pnodep;
    vector<int> pid_in_std_cell;  // Primitive node id under a StdCellandIONode
    switch (snode.type()) {
      case STD_INPUT: {
        // build one pri node
        pnodep = new PrimitiveGate;
        pid_in_std_cell.push_back(pri_cnt);
        set_pnode_basic_info(pnodep, pri_cnt, snode.stdId(), GType(T_INPUT), snode.name());
        vector<int> input_mapping = {-1};
        pnodep->setInputMapping(input_mapping);
        pnodep->setOutputMapping(0);
        snode.resizeOutputMappingPriId(1);
        snode.outputMappingPriId(0, pnodep->gid());  // record output mapping pri id
        _priNetlist.addNode(*pnodep);
        delete pnodep;
        break;
      }
      case STD_OUTPUT: {
        // build one pri node
        pnodep = new PrimitiveGate;
        pid_in_std_cell.push_back(pri_cnt);
        set_pnode_basic_info(pnodep, pri_cnt, snode.stdId(), GType(T_OUTPUT), snode.name());
        vector<int> input_mapping = {0};
        pnodep->setInputMapping(input_mapping);
        snode.resizeInputMappingPriId(1);
        snode.inputMappingPriId(0, pnodep->gid(), 0);  // record input mapping pri id and pin id
        _priNetlist.addNode(*pnodep);
        delete pnodep;
        break;
      }
      case STD_INOUT: {
        // build one input pri node
        pnodep = new PrimitiveGate;
        pid_in_std_cell.push_back(pri_cnt);
        set_pnode_basic_info(pnodep, pri_cnt, snode.stdId(), GType(T_INPUT), snode.name());
        vector<int> input_mapping = {-1};
        pnodep->setInputMapping(input_mapping);
        pnodep->setOutputMapping(0);
        snode.resizeOutputMappingPriId(1);
        snode.outputMappingPriId(0, pnodep->gid());  // record output mapping pri id
        _priNetlist.addNode(*pnodep);
        delete pnodep;

        // build one output pri node
        pnodep = new PrimitiveGate;
        pid_in_std_cell.push_back(pri_cnt);
        set_pnode_basic_info(pnodep, pri_cnt, snode.stdId(), GType(T_OUTPUT), snode.name());
        input_mapping = {0};
        pnodep->setInputMapping(input_mapping);
        snode.resizeInputMappingPriId(1);
        snode.inputMappingPriId(0, pnodep->gid(), 0);  // record input mapping pri id and pin id
        _priNetlist.addNode(*pnodep);
        delete pnodep;
        break;
      }
      case STD_CELL: {
        // find the corresponding StdCellModel using standard cell name
        scellp = _atpgLib->findCell(snode.stdCellName());
        snode.resizeInputMappingPriId(scellp->cellInputNum());
        snode.resizeOutputMappingPriId(scellp->cellOutputNum());

        // the port order decribed in the verilog file may not be the same as in std cell model
        // map their positions according to their names
        vector<int> input_id_map(snode.inPortCnt(), -1);    // map input node
        vector<int> output_id_map(snode.outPortCnt(), -1);  // map output node
        map_input_position(input_id_map, snode, scellp);
        map_output_position(output_id_map, snode, scellp);
        std_output_id_map[snode.stdId()] = output_id_map;  // store for step2

        for (int i = 0; i < scellp->primitiveNum(); i++) {
          pnodep = new PrimitiveGate;
          PrimitiveGate ref_pri_model = scellp->primitiveNode(i + scellp->cellInputNum());
          pid_in_std_cell.push_back(pri_cnt);
          set_pnode_basic_info(pnodep, pri_cnt, snode.stdId(), ref_pri_model.type(), snode.name());
          // update the pred in current StdCellandIONode
          for (auto id : ref_pri_model.preds()) {
            if (id < scellp->cellInputNum()) {    // inputs of std cell
              pnodep->addPred(input_id_map[id]);  // record port connected in order
                                                  // for connection that across the StdCellandIONode border,
                                                  // it will be handled in step 3
            } else if (!(id ^ tie1) || !(id ^ tie0) || !(id ^ tiex)) {
              pnodep->addPred(id);
            } else {  // internal pri gate of std cell
              pnodep->addPred(id - scellp->cellInputNum() + pid_in_std_cell[0]);
            }
          }
          // update output port mapping
          for (auto ref_output_id : ref_pri_model.succs()) {
            int offset = ref_output_id - (scellp->cellInputNum() + scellp->primitiveNum());
            if (offset >= 0) {  // an output port of std cell, recored outputMapping
              int output_id = output_id_map[offset];
              pnodep->setOutputMapping(output_id);
              snode.outputMappingPriId(output_id, pnodep->gid());  // record output mapping pri id
            }
          }
          // update input port mapping
          std::vector<int> temp_input_mapping;
          for (int pred_i = 0; pred_i < ref_pri_model.numPreds(); ++pred_i) {
            int ref_input_id = ref_pri_model.pred(pred_i);
            if (ref_input_id < scellp->cellInputNum()) {  // an input port of std cell, recored inputMapping
              int input_id = input_id_map[ref_input_id];
              temp_input_mapping.push_back(input_id);
              snode.inputMappingPriId(input_id, pnodep->gid(), pred_i);  // record input mapping pri id and pin id
            } else {
              temp_input_mapping.push_back(-1);
            }
          }
          pnodep->setInputMapping(temp_input_mapping);

          _priNetlist.addNode(*pnodep);
          delete pnodep;
        }
        // do not adjust to keep the order the same as in std cell model
        // adjust_output_id(pid_in_std_cell, scellp, scellp->primitiveNum() - scellp->cellOutputNum(), output_id_map);
        break;
      }
      default: {
        LOG_INFO << "unsupportted std cell type.";
        break;
      }
    }
    snode.std2Pri(pid_in_std_cell);
  }

  // dump time message of step 1
  // double time_delta = stats.elapsedRunTime();
  // LOG_INFO << "step1 time elapsed " << time_delta << "s";

  // step 2, traverse std cell netlist again for StdCellandIONode of type CELL/OUTPUT,
  //         derive the predcessors of the Primitive node that across the border
  std::map<std::string, int> inout_bus_pair;  // record added bus and its inout
  for (uint sid = 0; sid < std_node_vec.size(); sid++) {
    StdCellandIONode& snode = std_node_vec[sid];

    if ((STD_CELL == snode.type()) || (STD_OUTPUT == snode.type()) || (STD_INOUT == snode.type())) {
      vector<int> pidxinnode(snode.inPortCnt(), -1);  // used for CELL, record the related Primitive node id,
                                                      // the index is input node id in StdCellModel

      for (int pos = 0; pos < snode.inPortCnt(); pos++) {
        int pred_pid = 0;  // pred Primitive node id
        int predIdsize = snode.predId(pos).size();
        vector<int> multi_pid;
        for (int i = 0; i < predIdsize; ++i) {
          int pred_sid = snode.predId(pos)[i];
          int pred_sport = snode.predPort(pos)[i];
          StdCellandIONode& pred_snode = std_node_vec[pred_sid];
          if (!(pred_sid ^ tie1) || !(pred_sid ^ tie0) || !(pred_sid ^ tiex)) {
            pred_pid = pred_sid;
          } else if (STD_INPUT == pred_snode.type()) {
            pred_pid = pred_snode.std2Pri()[pred_sport];
          } else if (STD_INOUT == pred_snode.type()) {
            pred_pid = pred_snode.std2Pri()[pred_sport];
          } else {
            pred_pid = pred_snode.outputMappingPriId(std_output_id_map[pred_sid][pred_sport]);
          }
          multi_pid.push_back(pred_pid);
        }

        if (snode.type() == STD_INOUT) {
          int inout_pi_id = snode.std2Pri()[0];
          int inout_po_id = inout_pi_id + 1;
          auto inout_name = snode.name();
          auto it = inout_bus_pair.find(inout_name);

          if (it == inout_bus_pair.end()) {
            inout_bus_pair.insert(std::pair<std::string, int>(inout_name, pri_cnt));  // record added bus

            // add the bus node
            int bus_id = pri_cnt;
            PrimitiveGate* pnodep = new PrimitiveGate;
            set_pnode_basic_info(pnodep, pri_cnt, -1, GType(T_BUS), "ADD_BUS");
            vector<int> bus_preds(multi_pid);
            bus_preds.push_back(inout_pi_id);
            pnodep->preds(bus_preds);
            std::vector<int> temp_input_mapping = {-1};  // set input mapping
            pnodep->setInputMapping(temp_input_mapping);
            pnodep->netName("ADD_BUS_NET");
            _priNetlist.addNode(*pnodep);
            delete pnodep;

            // connect inout PO node to the added bus
            vector<int> preds_new = {bus_id};
            _priNetlist.gate(inout_po_id).preds(preds_new);

            // add a BUFZ after the bus node
            pnodep = new PrimitiveGate;
            set_pnode_basic_info(pnodep, pri_cnt, -1, GType(T_BUFZ), "ADD_BUFZ");
            pnodep->addPred(bus_id);
            temp_input_mapping = {-1};  // set input mapping
            pnodep->setInputMapping(temp_input_mapping);
            pnodep->netName("ADD_BUFZ_NET");
            _priNetlist.addNode(*pnodep);
            delete pnodep;
          }
        }

        //  1. in the case that an input is connected to multi nets,
        //     it may be inferred as a tri-state bus
        //  2. if all the pred Primitive nodes are TRI_BUF or TRI_INV,
        //     the net will be regarded as a tri-state bus
        //  3. if the pred is an inout
        //  4. add a new Primitive which type is BUS
        if (predIdsize > 1) {
          std::string inout_name;  // record inout name if there is inout
          int inout_pi_id;         // record inout PI gid if there is inout

          if (checkTriState(multi_pid)) {  // add bus if all the pred Primitive nodes are TRI_BUF or TRI_INV
            pred_pid = pri_cnt;
            PrimitiveGate* pnodep = new PrimitiveGate;
            set_pnode_basic_info(pnodep, pri_cnt, -1, GType(T_BUS), "ADD_BUS");
            pnodep->preds(multi_pid);
            std::vector<int> temp_input_mapping = {-1};  // set input mapping
            pnodep->setInputMapping(temp_input_mapping);
            pnodep->netName("ADD_BUS_NET");
            _priNetlist.addNode(*pnodep);
            delete pnodep;

          } else if (checkPredInout(snode, pos, inout_name, inout_pi_id)) {  // add bus if the pred is an inout
            auto it = inout_bus_pair.find(inout_name);
            if (it == inout_bus_pair.end()) {
              inout_bus_pair.insert(std::pair<std::string, int>(inout_name, pri_cnt));  // record added bus

              // add the bus node
              int bus_id = pri_cnt;
              PrimitiveGate* pnodep = new PrimitiveGate;
              set_pnode_basic_info(pnodep, pri_cnt, -1, GType(T_BUS), "ADD_BUS");
              pnodep->preds(multi_pid);
              std::vector<int> temp_input_mapping = {-1};  // set input mapping
              pnodep->setInputMapping(temp_input_mapping);
              pnodep->netName("ADD_BUS_NET");
              _priNetlist.addNode(*pnodep);
              delete pnodep;

              // connect inout PO node to the added bus
              int inout_po_id = inout_pi_id + 1;
              vector<int> preds_new = {bus_id};
              _priNetlist.gate(inout_po_id).preds(preds_new);

              // add a BUFZ after the bus node
              pred_pid = pri_cnt;
              pnodep = new PrimitiveGate;
              set_pnode_basic_info(pnodep, pri_cnt, -1, GType(T_BUFZ), "ADD_BUFZ");
              pnodep->addPred(bus_id);
              temp_input_mapping = {-1};  // set input mapping
              pnodep->setInputMapping(temp_input_mapping);
              pnodep->netName("ADD_BUFZ_NET");
              _priNetlist.addNode(*pnodep);
              delete pnodep;

            } else {  // already add one bus for this inout, the pred_pid should be the added BUFZ after the bus
              pred_pid = it->second + 1;
            }
          } else {
            LOG_ERROR << "there may be multi drives to the port of " << snode.name();
          }
        }

        if (STD_CELL == snode.type()) {
          pidxinnode[pos] = pred_pid;
        } else if (STD_OUTPUT == snode.type()) {
          int pid = snode.std2Pri()[pos];
          _priNetlist.gate(pid).addPred(pred_pid);
        }

        // record the std cell net name
        string netName(snode.predRef(pos));
        const std::map<std::string, int>& netName2pid = _priNetlist.netNameAndGid();
        auto it = netName2pid.find(netName);
        bool isTie = !(pred_pid ^ tie1) || !(pred_pid ^ tie0) || !(pred_pid ^ tiex);
        if ((it == netName2pid.end()) && (!isTie)) {
          _priNetlist.netNameAndGid(netName, pred_pid);
          _priNetlist.gate(pred_pid).netName(netName);
        }
      }

      // substitute input node id with corresponding Primitive node id
      if (STD_CELL == snode.type()) {
        for (auto pid : snode.std2Pri()) {
          auto preds = _priNetlist.gate(pid).preds();
          for (auto& pred : preds) {
            if (pred < snode.inPortCnt()) {
              pred = pidxinnode[pred];
            }
          }
          _priNetlist.gate(pid).preds(preds);
        }
      }
    }
  }

  // dump time message of step 2
  // double last_time = time_delta;
  // time_delta = stats.elapsedRunTime() - last_time;
  // LOG_INFO << "step2 time elapsed " << time_delta << "s";

  int tie1_id = -1;  // there is only one T_TIE0/1/X node with the id at the end
  int tie0_id = -1;
  int tiex_id = -1;
  vector<int> tie1node_succs, tie0node_succs, tiexnode_succs;

  // step 3, derive the successors of the Primitive node
  for (int pid = 0; pid < pri_cnt; pid++) {
    const PrimitiveGate& pnode = _priNetlist.gateConst(pid);
    vector<int> preds = pnode.preds();
    vector<int> preds_new(preds);  // change the tie node id in the pred vector if there are any
    for (uint i = 0; i < preds.size(); i++) {
      if (!(preds[i] ^ tie1)) {
        if (-1 == tie1_id) {  // make sure only add one T_TIE1 pri node
          tie1_id = _priNetlist.nodesConst().size();
          PrimitiveGate* tie1_pnodep = new PrimitiveGate;
          tie1_pnodep->gid(tie1_id);
          tie1_pnodep->type(T_TIE1);
          tie1_pnodep->netName("TIE1");
          tie1_pnodep->stdCellInstName("1'b1");
          _priNetlist.addNode(*tie1_pnodep);
          delete tie1_pnodep;
        }
        preds_new[i] = tie1_id;
        tie1node_succs.push_back(pid);
      } else if (!(preds[i] ^ tie0)) {
        if (-1 == tie0_id) {  // make sure only add one T_TIE0 pri node
          tie0_id = _priNetlist.nodesConst().size();
          PrimitiveGate* tie0_pnodep = new PrimitiveGate;
          tie0_pnodep->gid(tie0_id);
          tie0_pnodep->type(T_TIE0);
          tie0_pnodep->netName("TIE0");
          tie0_pnodep->stdCellInstName("1'b0");
          _priNetlist.addNode(*tie0_pnodep);
          delete tie0_pnodep;
        }
        preds_new[i] = tie0_id;
        tie0node_succs.push_back(pid);
      } else if (!(preds[i] ^ tiex)) {
        if (-1 == tiex_id) {  // make sure only add one T_TIE0 pri node
          tiex_id = _priNetlist.nodesConst().size();
          PrimitiveGate* tiex_pnodep = new PrimitiveGate;
          tiex_pnodep->gid(tiex_id);
          tiex_pnodep->type(T_TIEX);
          tiex_pnodep->netName("TIEX");
          tiex_pnodep->stdCellInstName("1'bx");
          _priNetlist.addNode(*tiex_pnodep);
          delete tiex_pnodep;
        }
        preds_new[i] = tiex_id;
        tiexnode_succs.push_back(pid);
      } else {
        PrimitiveGate& pred_pnode = _priNetlist.gate(preds[i]);
        pred_pnode.addSucc(pid);
      }
    }
    _priNetlist.gate(pid).preds(preds_new);

    // set those who have not corresponding std net name to "INTERNAL"
    PrimitiveGate& temp_pnode = _priNetlist.gate(pid);
    if (temp_pnode.netName() == "") {
      std::string net_name = "INTERNAL";
      temp_pnode.netName(net_name);
    }
  }

  // update tie nodes succs
  if (!tie1node_succs.empty()) {
    _priNetlist.gate(tie1_id).succs(tie1node_succs);
  }
  if (!tie0node_succs.empty()) {
    _priNetlist.gate(tie0_id).succs(tie0node_succs);
  }
  if (!tiexnode_succs.empty()) {
    _priNetlist.gate(tiex_id).succs(tiexnode_succs);
  }

  // dump time message of step 3
  // last_time = time_delta;
  // time_delta = stats.elapsedRunTime() - last_time;
  // LOG_INFO << "step3 time elapsed " << time_delta << "s";

  // STEP 4, simulate circuit based on tie nodes
  tie1_id = tie1;
  tie0_id = tie0;  // initial tie0_id and tie1_id
  std::set<int> found_tie0_nodes, found_tie1_nodes;

  for (int tie_id = (int) (_priNetlist.nodesConst().size() - 1); tie_id >= 0; tie_id--) {
    PrimitiveGate pnode = _priNetlist.gateConst(tie_id);
    if ((pnode.type() != T_TIE0) && (pnode.type() != T_TIE1)) {  // tie nodes are the final gates at this stage
      break;
    }
    if (pnode.type() == T_TIE0) {
      tie0_id = pnode.gid();
    } else {
      tie1_id = pnode.gid();
    }
  }

  if ((tie1_id != tie1) || (tie0_id != tie0)) {  // have tie node in pri netlist
    simPrimitiveNetlistBasedOnTieNodes(found_tie0_nodes, found_tie1_nodes);
    _priNetlist.foundTieNodes(found_tie0_nodes, found_tie1_nodes);
  }

  // dump time message of STEP 4
  // last_time = time_delta;
  // time_delta = stats.elapsedRunTime() - last_time;
  // LOG_INFO << "step4 time elapsed " << time_delta << "s";

  // step 5, process the built pri netlist:
  // 1. splite tie nodes
  // 2. reduce fanout if touching max fanout
  // 3. insert buffer if there are successive DFFs
  if (splitTieNodes()) {
    _priNetlist.splitTieNodes(maxFanout());  // insert more tie nodes if touching max fanout
  }
  _priNetlist.reduceFanout(maxFanout());  // insert more buffers if touching max fanout
  if (dffBuffers()) {
    _priNetlist.insertBuffersBetweenDFFs();
  }

  // dump time message of step 5
  // last_time = time_delta;
  // time_delta = stats.elapsedRunTime() - last_time;
  // LOG_INFO << "step5 time elapsed " << time_delta << "s";

  // dump pri netlist info for debug
  // std::string write_pri_netlist_file_path = "/home/linxiaoze/temp_files/pri_netlist_info";
  // writePrimitiveNetlistToFile(write_pri_netlist_file_path);

  _priNetlist.computeCircuitStatistics();
  // _priNetlist.printCktStatistics(true);

  LOG_INFO << "convert std cell netlist to primitive netlist end";
}

/**
 * @brief: Check if pred is tri-state.
 * @return bool
 */
bool NetlistManager::checkTriState(const vector<int>& multi_pid)
{
  for (int pid : multi_pid) {
    GType type = _priNetlist.gateConst(pid).type();
    if (T_TRISTATEBUF != type && T_TRISTATEINV != type) {
      return false;
    }
  }
  return true;
}

/**
 * @brief: Check if pred is inout.
 * @return bool
 */
bool NetlistManager::checkPredInout(const StdCellandIONode& snode, int port, std::string& inout_name, int& inout_pi_id)
{
  for (int id : snode.predId(port)) {
    StdCellandIONode& pred_snode = _stdCellNetlist.stdCellandIONode(id);
    if (STD_INOUT == pred_snode.type()) {
      inout_name = pred_snode.name();
      inout_pi_id = pred_snode.std2Pri()[0];
      return true;
    }
  }
  return false;
}

/**
 * @brief: Gate evaluation function.
 * @return none
 */
void NetlistManager::gateEval(const PrimitiveGate pnode, std::map<int, int>& gmvs, int& val)
{
  auto get_val = [](const std::map<int, int>& gmvs, int gid, int& val) { val = (gmvs.find(gid) != gmvs.end()) ? gmvs.at(gid) : VAL_X; };

  GType type = pnode.type();
  switch (type) {
    case T_NOT:
    case T_BUF:
    case T_OUTPUT: {
      int pred_val;
      get_val(gmvs, pnode.pred(0), pred_val);
      val = TruthTable1[type][pred_val];
      break;
    }
    case T_TRISTATEBUF:
    case T_TRISTATEINV: {  // (OE, I)
      int oe_val, in_val;
      get_val(gmvs, pnode.pred(0), oe_val);
      get_val(gmvs, pnode.pred(1), in_val);
      val = (oe_val == VAL_ONE) ? in_val : (oe_val == VAL_ZERO) ? VAL_Z : VAL_X;
      val = (type == T_TRISTATEINV) ? TruthTable1[T_NOT][val] : val;
      break;
    }
    case T_AND:
    case T_NAND:
    case T_OR:
    case T_NOR:
    case T_XOR:
    case T_XNOR: {
      int pred_val, pred_val_n;
      get_val(gmvs, pnode.pred(0), pred_val);
      get_val(gmvs, pnode.pred(1), pred_val_n);
      val = TruthTable2[type][pred_val][pred_val_n];
      for (int sid = 2; sid < pnode.numSuccs(); ++sid) {
        get_val(gmvs, pnode.pred(sid), pred_val_n);
        val = TruthTable2[type][val][pred_val_n];
      }
      break;
    }
    case T_MUX_2: {  // (SEL, I1, I2)
      int sel_val, in1_val, in2_val;
      get_val(gmvs, pnode.pred(0), sel_val);
      get_val(gmvs, pnode.pred(1), in1_val);
      get_val(gmvs, pnode.pred(2), in2_val);
      if (sel_val == VAL_ZERO) {
        val = in1_val;
      } else if (sel_val == VAL_ONE) {
        val = in2_val;
      } else {
        if (in1_val == in2_val) {
          val = in1_val;
        } else {
          val = VAL_X;
        }
      }
      break;
    }
    case T_BUS:
    case T_DLA_PRI:
    case T_DFF_PRI: {
      break;
    }
    default: {
      LOG_ERROR << "unsupported primitive gate type " << type;
      break;
    }
  }
}

/**
 * @brief: Simulate primitive netlist based on tie nodes.
 * @return none
 */
void NetlistManager::simPrimitiveNetlistBasedOnTieNodes(std::set<int>& found_tie0_nodes, std::set<int>& found_tie1_nodes)
{
  std::queue<int> event_queue;
  std::map<int, int> gmvs;  // no need to prepare val for all gates, "map" is better

  auto pri_netlist = _priNetlist.nodesConst();

  auto pop_front = [](std::queue<int>& event_queue, int& pnode_id) {
    pnode_id = event_queue.front();
    event_queue.pop();
  };

  auto get_val = [](const std::map<int, int>& gmvs, int gid, int& val) { val = (gmvs.find(gid) != gmvs.end()) ? gmvs.at(gid) : VAL_X; };

  // push tie nodes' succs into event queue
  for (int tie_id = (int) (pri_netlist.size() - 1); tie_id >= 0; tie_id--) {
    PrimitiveGate pnode = pri_netlist[tie_id];
    if ((pnode.type() != T_TIE0) && (pnode.type() != T_TIE1)) {  // tie nodes are the final gates at this stage
      break;
    }
    int val = (pnode.type() == T_TIE0) ? VAL_ZERO : VAL_ONE;
    gmvs.insert(std::pair<int, int>(pnode.gid(), val));  // set tie node val
    for (int succ_id : pnode.succs()) {
      if (pri_netlist[succ_id].type() != T_OUTPUT) {
        event_queue.push(succ_id);
      }
    }
  }

  int event_id;                   // current_event_id
  while (!event_queue.empty()) {  // event-driven logic simulation
    pop_front(event_queue, event_id);
    PrimitiveGate pnode = pri_netlist[event_id];
    int old_val;
    get_val(gmvs, pnode.gid(), old_val);
    int new_val = VAL_X;
    gateEval(pnode, gmvs, new_val);
    if ((old_val != new_val) && (new_val != VAL_X)) {
      gmvs.insert(std::pair<int, int>(event_id, new_val));  // update val of event_id
      for (int sid = 0; sid < pnode.numSuccs(); ++sid) {    // push succs into event queue
        int succ_id = pnode.succ(sid);
        if (pri_netlist[succ_id].type() != T_OUTPUT) {
          event_queue.push(succ_id);
        }
      }
      if (new_val == VAL_ONE) {
        found_tie1_nodes.insert(event_id);
      } else if (new_val == VAL_ZERO) {
        found_tie0_nodes.insert(event_id);
      }
    }
  }
}

/**
 * @brief: Write primitive netlist to file.
 * @return none
 */
void NetlistManager::writePrimitiveNetlistToFile(std::string file_name)
{
  std::ofstream fp(file_name);
  if (fp.fail()) {
    LOG_ERROR << "failed to write stdcells information file '" << file_name << "'";
  }
  fp << "Primitive netlist info:\n\n";
  for (auto pnode : _priNetlist.nodesConst()) {
    fp << "gid: " << pnode.gid() << ", type: " << LUT_GTypeToName[pnode.type()] << ", std inst name: " << pnode.stdCellInstName() << "\n";
    fp << "  num of preds: " << pnode.numPreds() << "\n";
    for (int pred_id = 0; pred_id < pnode.numPreds(); ++pred_id) {
      fp << "    the " << pred_id << " pred id: " << pnode.pred(pred_id)
         << ", from: " << _priNetlist.nodesConst()[pnode.pred(pred_id)].stdCellInstName() << " - "
         << _priNetlist.nodesConst()[pnode.pred(pred_id)].netName() << "\n";
    }
    fp << "  num of succs: " << pnode.numSuccs() << "\n";
    for (int succ_id = 0; succ_id < pnode.numSuccs(); ++succ_id) {
      fp << "    the " << succ_id << " succ id: " << pnode.succ(succ_id)
         << ", to: " << _priNetlist.nodesConst()[pnode.succ(succ_id)].stdCellInstName() << " - "
         << _priNetlist.nodesConst()[pnode.succ(succ_id)].netName() << "\n";
    }
    fp << "  input mapping:\n";
    for (int input_id = 0; input_id < (int) pnode.inputMapping().size(); ++input_id) {
      fp << "    the " << input_id << " input mapping: " << pnode.inputMapping()[input_id] << "\n";
    }
    fp << "  output mapping:\n";
    fp << "    the output mapping: " << pnode.outputMapping() << "\n\n";
  }
  fp.close();
}

/**
 * @brief: Optimize and levelize netlist.
 * @return none
 */
void NetlistManager::genLevelizedNetlist(bool print_debug_info)
{
  LOG_INFO << "generate levelized netlist start";
  _simNetlist.clear();

  std::vector<PrimitiveGate> tempNodes(primitiveNetlist().nodesConst());
  if (print_debug_info) {
    LOG_INFO << "#### original copied netlist #####";
    // dumpNetlistOfPrimitiveGates(tempNodes);
  }

  // 1. Identify combinational loops
  //    TBD: 1. store the information for future enhancement
  //         2. delay the opertation until pin/cell constraints are declared
  LOG_INFO << "identify feedback regions in circuit";
  std::vector<std::vector<int>> sccs, loops, fbbufs_per_scc, latches_per_scc;
  identifyFeedbackRegions(tempNodes, sccs);                                  // find sccs
  identifyCombinationalLoops(sccs, loops, fbbufs_per_scc, latches_per_scc);  // find loops in scc

  if (loops.size() > 0) {
    std::set<int> add_tiex_loops;
    addTIEXInConstantValueLoop(loops, fbbufs_per_scc, add_tiex_loops);
    assert(add_tiex_loops.size() <= loops.size());

    std::vector<int> depth_to_po;
    depth_to_po.assign(primitiveNetlist().nodesConst().size(), -1);
    calculateDepthToPO(depth_to_po);
    addFeedbackBufInLoop(loops, fbbufs_per_scc, depth_to_po, add_tiex_loops);

    identifyPotentialOscillateLoops(loops, add_tiex_loops);

    // update tempNodes
    tempNodes.assign(primitiveNetlist().nodesConst().begin(), primitiveNetlist().nodesConst().end());
  }

  // 2. Levelize optimized netlist
  LOG_INFO << "levelize optimized netlist";
  std::vector<PrimitiveGate> postLevelOptNodes;
  std::vector<int> levels, levels_post_opt;
  levelizeOptimizedNodes(tempNodes, postLevelOptNodes, levels, levels_post_opt);
  if (print_debug_info) {
    LOG_INFO << "#### Post opt netlist #####";
    // dumpNetlistOfPrimitiveGates(postLevelOptNodes);
  }

  // 3. populate _optNodes of vector<Gate>
  LOG_INFO << "construct optimized netlist";
  populateOptNodesForSimulation(postLevelOptNodes, levels_post_opt);
  _simNetlist.populateNetlistVec();  // create vectors for OpenCL kernels
  // _simNetlist.printCktStatistics(false);  // 'false' means not show info of opencl
  LOG_INFO << "generate levelized netlist end";
}

/**
 * @brief: Identify feedback regions in the circuit.
 * @return none
 */
void NetlistManager::identifyFeedbackRegions(std::vector<PrimitiveGate>& cktnodes, std::vector<std::vector<int>>& sccs)
{
  assert(sccs.size() == 0);
  // build directed graph out of netlist topology
  d_graph_t graph;
  std::vector<vertex_t> vertices;
  for (size_t i = 0; i < cktnodes.size(); i++) {
    vertices.push_back(boost::add_vertex(graph));  // construct vertices
  }
  for (size_t i = 0; i < cktnodes.size(); i++) {
    const PrimitiveGate& node = cktnodes[i];
    for (size_t s = 0; s < node.succs().size(); s++) {
      int sink = node.succs()[s];
      // break the edge intentionly when sink is TIE, DE, DFF, or DLA
      switch (cktnodes[sink].type()) {
        case T_TIE0:
        case T_TIE1:
        case T_TIEX:
        case T_TIEZ:
        case T_DELAY_ELEMENT:
        case T_DLA_PRI:
        case T_DFF_PRI: {
          break;
        }
        default: {
          boost::add_edge(vertices.at(i), vertices.at(sink), graph);
        }
      }
    }
  }

  // get scc
  graph[boost::graph_bundle].number_of_components = boost::strong_components(graph, boost::get(&node_properties::component, graph));

  int num_sccs = graph[boost::graph_bundle].number_of_components;
  std::vector<std::vector<int>> temp_sccs(num_sccs);
  // collect nodes per strong component
  auto nodes = boost::vertices(graph);
  for (auto it = nodes.first; it != nodes.second; ++it) {
    temp_sccs[graph[*it].component].push_back(*it);
  }
  for (size_t c = 0; c < temp_sccs.size(); c++) {
    const size_t c_size = temp_sccs[c].size();
    if (c_size > 1) {
      sccs.push_back(temp_sccs[c]);
    }
  }
  // std::cout << "\033[49;32mTotal strong connected component: " << sccs.size() << "\033[0m" << std::endl;
}

/**
 * @brief: Identify combinational loops.
 * @return none
 */
void NetlistManager::identifyCombinationalLoops(const std::vector<std::vector<int>>& sccs, std::vector<std::vector<int>>& loops,
                                                std::vector<std::vector<int>>& fbbufs_per_scc,
                                                std::vector<std::vector<int>>& latches_per_scc)
{
  const std::vector<PrimitiveGate> pri_nodes = primitiveNetlist().nodesConst();
  // seperate loops in sccs
  for (auto scc : sccs) {
    for (auto gid : scc) {
      PrimitiveGate pnode = pri_nodes[gid];
      for (auto succ_id : pnode.succs()) {
        std::vector<int> loop;
        std::set<int> visit;
        loop.push_back(pnode.gid());
        visit.insert(succ_id);
        PrimitiveGate succ_pnode = pri_nodes[succ_id];
        bool have_loop = false;
        have_loop = succDepthFirstSearch(loop, visit, succ_pnode, pnode.gid());
        if (have_loop) {
          loops.push_back(loop);
        }
      }
    }
  }

  // delete repeat loop
  for (uint loop_id = 0; loop_id < loops.size(); loop_id++) {
    std::vector<int> curr_loop = loops[loop_id];
    // traverse latter loops
    for (uint other_loop_id = (loop_id + 1); other_loop_id < loops.size(); ++other_loop_id) {
      if (curr_loop.size() != loops[other_loop_id].size()) {
        continue;
      }
      std::vector<int> other_loop = loops[other_loop_id];
      bool is_repeat_loop = true;
      int start_in_other_loop = 0;
      // traverse gates in other loop
      for (uint gid_other_loop = 0; gid_other_loop < other_loop.size(); ++gid_other_loop) {
        if (curr_loop[0] != other_loop[gid_other_loop]) {
          if (gid_other_loop == (other_loop.size() - 1)) {  // no element is the same as curr_loop[0]
            is_repeat_loop = false;
          }
          continue;
        }
        start_in_other_loop = gid_other_loop;
        break;
      }
      if (!is_repeat_loop) {
        continue;
      }
      for (uint gid_curr_loop = 0; gid_curr_loop < curr_loop.size(); ++gid_curr_loop) {
        uint gid_other_loop = start_in_other_loop + gid_curr_loop;
        gid_other_loop = (gid_other_loop >= other_loop.size()) ? (gid_other_loop - (int) other_loop.size()) : gid_other_loop;
        if (curr_loop[gid_curr_loop] != other_loop[gid_other_loop]) {
          is_repeat_loop = false;
        }
      }
      if (is_repeat_loop) {
        loops.erase(loops.begin() + other_loop_id);
        other_loop_id--;
      }
    }
  }

  // reverse loop to conform topological and collect fbbuf and latch info
  for (auto& loop : loops) {
    std::reverse(loop.begin(), loop.end());

    std::vector<int> fbufs;
    std::vector<int> latches;
    for (int gid_loop = 0; gid_loop < (int) loop.size(); ++gid_loop) {
      int gid = loop[gid_loop];
      if (pri_nodes[gid].type() == T_BUF_FB) {
        fbufs.push_back(gid);
      } else if (pri_nodes[gid].type() == T_DLA_PRI) {
        latches.push_back(gid);
      }
    }
    fbbufs_per_scc.push_back(fbufs);
    latches_per_scc.push_back(latches);
  }

  primitiveNetlist().loops(loops);

  // print info of loops
  if (!loops.empty()) {
    LOG_WARNING << "found " << loops.size() << " loop(s) in the netlist";
    std::cout << "\033[49;32mTotal Loops: " << loops.size() << "\033[0m" << std::endl;
    for (auto loop : loops) {
      std::cout << "\033[49;32mLoop: ";
      for (auto gid : loop) {
        PrimitiveGate pnode = primitiveNetlist().gateConst(gid);
        std::cout << pnode.stdCellInstName() << " -> ";
      }
      PrimitiveGate pnode = primitiveNetlist().gateConst(loop[0]);
      std::cout << pnode.stdCellInstName() << "\033[0m" << std::endl;
    }
  }
}

/**
 * @brief: Identify combinational loops.
 * @return none
 */
bool NetlistManager::succDepthFirstSearch(std::vector<int>& loop, std::set<int>& visit, const PrimitiveGate curr_pnode, const int source_id)
{
  visit.insert(curr_pnode.gid());
  if (curr_pnode.gid() == source_id) {
    return true;
  }

  for (auto succ_id : curr_pnode.succs()) {
    if (visit.find(succ_id) != visit.end()) {
      return false;
    }
    bool should_continue = false;
    PrimitiveGate succ_pnode = primitiveNetlist().gateConst(succ_id);
    should_continue = succDepthFirstSearch(loop, visit, succ_pnode, source_id);
    if (should_continue) {
      loop.push_back(curr_pnode.gid());
      return true;
    } else {
      continue;
    }
  }
  return false;
}

/**
 * @brief: Add TIEX node in constant value loop.
 * @return none
 */
void NetlistManager::addTIEXInConstantValueLoop(const std::vector<std::vector<int>>& loops,
                                                const std::vector<std::vector<int>>& fbbufs_per_scc, std::set<int>& add_tiex_loops)
{
  std::set<int> found_tie0_nodes = _priNetlist.foundTie0Nodes();
  std::set<int> found_tie1_nodes = _priNetlist.foundTie1Nodes();

  // traverse all the loops
  for (int loop_id = 0; loop_id < (int) loops.size(); ++loop_id) {
    if (fbbufs_per_scc[loop_id].size()) {
      continue;  // already have fbbuf in the loop
    }
    std::vector<int> loop = loops[loop_id];
    bool add_tiex_tag = false;  // only need to add one TIEX
    // traverse gates in the current loop
    for (uint gid_loop = 0; gid_loop < loop.size(); ++gid_loop) {
      int gid = loop[gid_loop];  // current gate id
      if ((found_tie0_nodes.find(gid) == found_tie0_nodes.end()) && (found_tie1_nodes.find(gid) == found_tie1_nodes.end())) {
        continue;  // not const value node
      }
      PrimitiveGate pnode_const = _priNetlist.gate(gid);  // a const value node
      int num_preds = pnode_const.numPreds();
      for (int pid = 0; pid < num_preds; ++pid) {
        int pred_id = pnode_const.pred(pid);
        PrimitiveGate pred_pnode = _priNetlist.gate(pred_id);
        // insert TIEX node
        if ((pred_pnode.type() == T_TIE1) || (pred_pnode.type() == T_TIE0)) {
          int last_gid_in_loop = (gid_loop == 0) ? (loop.size() - 1) : (gid_loop - 1);
          for (int other_pid = 0; other_pid < num_preds; ++other_pid) {
            if (other_pid == pid) {
              continue;
            }
            int other_pred_id = pnode_const.pred(other_pid);
            if (other_pred_id == loop[last_gid_in_loop]) {  // find the last node in loop, insert TIEX
              _priNetlist.insertTIEXAfterNode(other_pred_id, gid);
              add_tiex_tag = true;
              add_tiex_loops.insert(loop_id);  // mark the loop as already add TIEX
              break;
            }
          }
          if (add_tiex_tag) {
            break;  // no need to traverse the left preds
          }
        }
      }
      if (add_tiex_tag) {
        break;  // no need to traverse the left nodes in the current loop
      }
    }
  }
}

/**
 * @brief: Calculate depth of nodes to PO.
 * @return none
 */
void NetlistManager::calculateDepthToPO(std::vector<int>& depth_to_po)
{
  const std::vector<PrimitiveGate> pri_nodes = primitiveNetlist().nodesConst();
  std::queue<int> depth_queue;

  auto pop_front = [](std::queue<int>& depth_queue, int& pnode_id) {
    pnode_id = depth_queue.front();
    depth_queue.pop();
  };

  for (auto pnode : pri_nodes) {
    if (pnode.type() == T_OUTPUT) {
      depth_to_po[pnode.gid()] = 0;
      for (auto pred_id : pnode.preds()) {
        depth_queue.push(pred_id);
      }
    }
  }
  while (!depth_queue.empty()) {
    int pid = -1, min_depth = -1;
    pop_front(depth_queue, pid);
    PrimitiveGate pnode = pri_nodes[pid];
    min_depth = depth_to_po[pid];
    for (auto succ_id : pnode.succs()) {
      if (depth_to_po[succ_id] == -1) {
        continue;
      }
      if ((-1 == min_depth) || (min_depth > (depth_to_po[succ_id] + 1))) {
        min_depth = depth_to_po[succ_id] + 1;
      }
    }
    depth_to_po[pid] = min_depth;
    for (auto pred_id : pnode.preds()) {
      if (-1 == depth_to_po[pred_id]) {
        depth_queue.push(pred_id);
      }
    }
  }
}

/**
 * @brief: Add feedback buffer in loop.
 * @return none
 */
void NetlistManager::addFeedbackBufInLoop(const std::vector<std::vector<int>>& loops, const std::vector<std::vector<int>>& fbbufs_per_scc,
                                          const std::vector<int> depth_to_po, const std::set<int>& add_tiex_loops)
{
  // traverse all the loops
  for (int loop_id = 0; loop_id < (int) loops.size(); ++loop_id) {
    if ((fbbufs_per_scc[loop_id].size()) || (add_tiex_loops.find(loop_id) != add_tiex_loops.end())) {
      continue;  // already have fbbuf or tiex in the loop
    }
    std::vector<int> loop = loops[loop_id];
    uint gid_with_min_depth = 0;
    // traverse gates in the current loop and find the gate with min depth to PO
    for (int gid_loop = 0; gid_loop < (int) loop.size(); ++gid_loop) {
      if (depth_to_po[loop[gid_with_min_depth]] > depth_to_po[loop[gid_loop]]) {
        gid_with_min_depth = gid_loop;
      }
    }
    // insert FB_BUF
    uint next_gid_in_loop = (gid_with_min_depth == (loop.size() - 1)) ? 0 : (gid_with_min_depth + 1);
    primitiveNetlist().insertFeedbackBufAfterNode(loop[gid_with_min_depth], loop[next_gid_in_loop]);
  }
}

/**
 * @brief: Identify potential oscillate loops.
 * @return none
 */
void NetlistManager::identifyPotentialOscillateLoops(const std::vector<std::vector<int>>& loops, const std::set<int>& add_tiex_loops)
{
  std::vector<int> potential_oscillate_loops;
  std::vector<PrimitiveGate> pri_nodes = primitiveNetlist().nodesConst();
  for (int loop_id = 0; loop_id < (int) loops.size(); loop_id++) {
    if (add_tiex_loops.find(loop_id) != add_tiex_loops.end()) {
      continue;
    }
    std::vector<int> loop = loops[loop_id];
    bool is_potential_oscillate = false;
    int num_inverse = 0;
    for (auto gid : loop) {
      PrimitiveGate pnode = pri_nodes[gid];
      switch (pnode.type()) {
        case T_XOR:
        case T_XNOR:
        case T_MUX_2: {
          is_potential_oscillate = true;
          break;
        }
        case T_NOT:
        case T_NAND:
        case T_NOR: {
          num_inverse++;
          break;
        }
        default: {
          break;
        }
      }
      if (is_potential_oscillate) {
        break;
      }
    }
    if (num_inverse % 2 != 0) {
      is_potential_oscillate = true;
    }
    if (is_potential_oscillate) {
      potential_oscillate_loops.push_back(loop_id);
    }
  }
  primitiveNetlist().potentialOscillateLoops(potential_oscillate_loops);
}

/**
 * @brief: Identify feedback regions in the circuit.
 * @return none
 */
void NetlistManager::levelizeOptimizedNodes(const std::vector<PrimitiveGate>& pre_level_optnodes,
                                            std::vector<PrimitiveGate>& post_level_optnodes, std::vector<int>& levels,
                                            std::vector<int>& levels_post_opt)
{
  assert(post_level_optnodes.size() == 0 && levels.size() == 0 && levels_post_opt.size() == 0);

  computeLevelOfOptimizedNodes(pre_level_optnodes, levels);

  primitiveNetlist().computeCircuitStatistics();  // compute statistics of pre-adjust ckt

  // Create post_level_optnodes and update id_map
  if (generatePostLevelOptNodesWithTypeGrouping(pre_level_optnodes, levels, post_level_optnodes, levels_post_opt)) {
    LOG_ERROR << "generate post level netlist fail";
  }
}

/**
 * @brief: Compute level of optimized nodes.
 * @return none
 */
void NetlistManager::computeLevelOfOptimizedNodes(const std::vector<PrimitiveGate>& pre_level_optnodes, std::vector<int>& level)
{
  // Obtain starting nodes of level 0 and output nodes
  std::vector<int> input_nodes, tie_nodes, output_nodes, de_nodes, dff_nodes, dla_nodes, fbbuf_nodes;

  compileNodeInfoForLevelization(pre_level_optnodes, input_nodes, tie_nodes, output_nodes, de_nodes, dff_nodes, dla_nodes, fbbuf_nodes);

  // start_pts[] store the gate ids where levelization start
  std::vector<int> start_pts(input_nodes);
  start_pts.insert(start_pts.end(), tie_nodes.begin(), tie_nodes.end());
  start_pts.insert(start_pts.end(), de_nodes.begin(), de_nodes.end());
  start_pts.insert(start_pts.end(), dff_nodes.begin(), dff_nodes.end());
  start_pts.insert(start_pts.end(), dla_nodes.begin(), dla_nodes.end());
  start_pts.insert(start_pts.end(), fbbuf_nodes.begin(), fbbuf_nodes.end());

  // init "visited" flag and "level" # for each node
  std::vector<int> visited(pre_level_optnodes.size(), 0);
  level.assign(pre_level_optnodes.size(), -1);

  // in_edges store preds of each node; a pred is removed once it is visited
  std::vector<std::vector<int>> in_edges;
  for (size_t i = 0; i < pre_level_optnodes.size(); i++) {
    in_edges.push_back(pre_level_optnodes[i].preds());
  }
  // init Queue with PIs, TIEs, DEs, DFFs, and DLatches (BUF_FB)
  std::queue<int> queue;
  for (size_t s = 0; s < start_pts.size(); s++) {
    level[start_pts[s]] = 0;         // assign start points with lev 0
    in_edges[start_pts[s]].clear();  //   and clear its in-edges
    queue.push(start_pts[s]);
  }
  while (!queue.empty()) {
    int cid = queue.front();  // current gate id
    queue.pop();
    const PrimitiveGate& gat = pre_level_optnodes[cid];
    for (int s = 0; s < gat.numSuccs(); s++) {
      int sid = (gat.succs())[s];
      const PrimitiveGate& sgat = pre_level_optnodes[sid];
      if (0 == level[sid]) {
        continue;
      }  // DONOT loop over level 0 node
      assert(sid < (int) pre_level_optnodes.size());
      auto it = std::find(in_edges[sid].begin(), in_edges[sid].end(), cid);
      while (it != in_edges[sid].end()) {
        // a node can be a gate's fanin more than once
        in_edges[sid].erase(it);
        it = std::find(in_edges[sid].begin(), in_edges[sid].end(), cid);
      }
      if (in_edges[sid].size() == 0) {
        queue.push(sid);
        // compute new_level as all predecessors are touched
        int new_l = 0;
        for (int p = 0; p < sgat.numPreds(); p++) {
          int pid = sgat.preds()[p];
          if (new_l < level[pid]) {
            new_l = level[pid];
          }
        }
        level[sid] = new_l + 1;
      }
    }
  }
  // As SCCs or feedback loops are tagged previously, all nodes should
  //   be assigned with a level number
  std::vector<int> abnormal_gids;
  for (size_t g = 0; g < level.size(); g++) {
    if (-1 == level[g]) {
      abnormal_gids.push_back(g);
    }
  }
  if (abnormal_gids.size()) {
    for (auto it = abnormal_gids.begin(); it != abnormal_gids.end(); ++it) {
      LOG_ERROR << "gates " << *it << " with unassigned level";
      // LOG_ERROR << " " << *it + 1;
    }
  }

  // adjust level # for sequential elements and outputs
  int max_level = *std::max_element(level.begin(), level.end());
  for (int i = 0; i < (int) level.size(); i++) {
    if (level[i] < max_level) {
      continue;
    }
    const PrimitiveGate& gat = pre_level_optnodes[i];
    if (gat.type() != T_OUTPUT) {
      max_level++;  // make sure OUTPUT level towards the end
      break;
    }
  }
  primitiveNetlist().maxLevel(max_level);
  int last_level = max_level;
  if (output_nodes.size()) {  // adjust OUTPUT level towards the end
    for (size_t i = 0; i < output_nodes.size(); i++) {
      level[output_nodes[i]] = last_level;
    }
    last_level++;
  }
  if (fbbuf_nodes.size() || dla_nodes.size()) {  // fbbuf & dla after OUTPUT
    for (size_t i = 0; i < fbbuf_nodes.size(); i++) {
      level[fbbuf_nodes[i]] = last_level;
    }
    for (size_t i = 0; i < dla_nodes.size(); i++) {
      level[dla_nodes[i]] = last_level;
    }
    last_level++;
  }
  if (dff_nodes.size() || de_nodes.size()) {         // dff or DE after fbbuf & dla
    for (size_t i = 0; i < dff_nodes.size(); i++) {  // DE and dff/dla are
      level[dff_nodes[i]] = last_level;              //   mutual exclusive
    }
    for (size_t i = 0; i < de_nodes.size(); i++) {
      level[de_nodes[i]] = last_level;
    }
  }
}

/**
 * @brief: Compile nodes info for levelizaion.
 * @return none
 */
void NetlistManager::compileNodeInfoForLevelization(const std::vector<PrimitiveGate>& pre_level_optnodes, std::vector<int>& input_nodes,
                                                    std::vector<int>& tie_nodes, std::vector<int>& output_nodes,
                                                    std::vector<int>& de_nodes,     // delay elements
                                                    std::vector<int>& dff_nodes,    // D flip flops
                                                    std::vector<int>& dla_nodes,    // D latches
                                                    std::vector<int>& fbbuf_nodes)  // feedback buffers
{
  for (size_t n = 0; n < pre_level_optnodes.size(); n++) {
    const PrimitiveGate& gat = pre_level_optnodes[n];
    switch (gat.type()) {
      case T_INPUT: {
        input_nodes.push_back(n);
        break;
      }
      case T_OUTPUT: {
        output_nodes.push_back(n);
        break;
      }
      case T_TIE1:
      case T_TIE0:
      case T_TIEX:
      case T_TIEZ: {
        tie_nodes.push_back(n);
        break;
      }
      case T_DELAY_ELEMENT: {
        de_nodes.push_back(n);
        break;
      }
      case T_DFF_PRI: {
        dff_nodes.push_back(n);
        break;
      }
      case T_DLA_PRI: {
        dla_nodes.push_back(n);
        break;
      }
      case T_BUF_FB: {  // TBD: redo feedback loop treatment later
        fbbuf_nodes.push_back(n);
        break;
      }
      default: {
        break;
      }
    }
  }

  if ((input_nodes.size() + tie_nodes.size()) == 0) {
    LOG_WARNING << "The circuit has no input and tie node.";
  }
}

/**
 * @brief: Generate post level optimized nodes with type grouping.
 * @return status
 */
int NetlistManager::generatePostLevelOptNodesWithTypeGrouping(const std::vector<PrimitiveGate>& pre_level_optnodes,
                                                              const std::vector<int>& levels,
                                                              std::vector<PrimitiveGate>& post_level_optnodes,
                                                              std::vector<int>& levels_post_opt)
{
  int max_level = *std::max_element(levels.begin(), levels.end());
  // idmap_pre2post[] is index map from pre_level_nodes to post_level_nodes
  std::vector<int> idmap_pre2post(pre_level_optnodes.size(), -1);
  std::vector<int> idmap_post2pre(pre_level_optnodes.size(), -1);
  size_t base = 0;
  // TBD: algorithm complexity is #levels * #nodes;
  //   It can be improved to 2 * #nodes.
  //   1. scan the nodes to place gids in the order of increasing level#
  //   2. scan gids at each level to implement gate type grouping
  for (int lev = 0; lev <= max_level; lev++) {
    // allow for maximum 6-input gate
    int max_inputs = 6;
    std::vector<int> empty_vec(0, 0);
    std::vector<std::vector<int>> cnt_in(max_inputs, empty_vec);
    int n_types = (int) T_LAST_GTYPE;
    // Model as 3D arrary [gate_type][num_inputs][gate_count]
    std::vector<std::vector<std::vector<int>>> group_gates(n_types, cnt_in);
    for (size_t n = 0; n < pre_level_optnodes.size(); n++) {
      if (levels[n] != lev) {
        continue;  // Skip gates whose level is not the target level
      }
      const PrimitiveGate& cgat = pre_level_optnodes[n];
      switch (cgat.type()) {
        // The following GTypes either have only one scenario of input
        //   count( such as 0 or 1 input ) or gate evaluation does not
        //   depend on input values( such as TIE gates )
        case T_OUTPUT:
        case T_NOT:
        case T_BUF:
        case T_BUFZ:
        case T_INPUT:  // 0 inputs
        case T_DELAY_ELEMENT:
        case T_BUF_FB:
        case T_TIE1:
        case T_TIE0:
        case T_TIEX:
        case T_TIEZ:
        case T_TRISTATEBUF:  // 2 inputs
        case T_TRISTATEINV:  // 2 inputs
        case T_MUX_2:        // 3 inputs
        case T_DLA_PRI:      // 4 inputs
        case T_DFF_PRI:      // 4 inputs
        {
          group_gates[(int) cgat.type()][0].push_back(n);
          break;
        }
        // The following GTypes, input count can vary
        case T_XOR:
        case T_XNOR:
        case T_AND:
        case T_NAND:
        case T_OR:
        case T_NOR:
        case T_BUS:
        case T_BUS_GOHIGH:
        case T_BUS_GOLOW:
        case T_BUS_X: {
          int num_preds = cgat.numPreds();
          group_gates[(int) cgat.type()][num_preds - 1].push_back(n);
          break;
        }
        default: {
          LOG_ERROR << "unexpected gate type '" << LUT_GTypeToName[cgat.type()] << "'"
                    << " on gate " << n;
          return STATUS_ERROR;
        }
      }
    }
    // gate id per pre_level_optnodes[] in current level
    std::vector<int> gid_in_lev;
    // handle gate type with fixed input size or TIE gate
    std::vector<int> leading_types({T_INPUT, T_OUTPUT, T_TIE1, T_TIE0, T_TIEZ, T_TIEX, T_NOT, T_BUF, T_BUFZ, T_TRISTATEBUF, T_TRISTATEINV,
                                    T_MUX_2, T_BUF_FB, T_DLA_PRI, T_DFF_PRI, T_DELAY_ELEMENT});
    for (size_t t = 0; t < leading_types.size(); t++) {
      int tp = leading_types[t];
      gid_in_lev.insert(gid_in_lev.end(), group_gates[tp][0].begin(), group_gates[tp][0].end());
    }
    // handle gate type with varied input size
    std::vector<int> middle_types({T_XOR, T_XNOR, T_AND, T_NAND, T_OR, T_NOR, T_BUS, T_BUS_GOHIGH, T_BUS_GOLOW, T_BUS_X});
    for (size_t t = 0; t < middle_types.size(); t++) {
      int tp = middle_types[t];
      for (int ins = 0; ins < max_inputs; ins++) {
        gid_in_lev.insert(gid_in_lev.end(), group_gates[tp][ins].begin(), group_gates[tp][ins].end());
      }
    }
    // gid_in_lev has relative position of gate in current level
    for (size_t p = 0; p < gid_in_lev.size(); p++) {
      idmap_pre2post[gid_in_lev[p]] = (int) (base + p);
      idmap_post2pre[base + p] = gid_in_lev[p];
    }
    base += gid_in_lev.size();
  }

  // Generate post_level_optnodes[] based on idmap_pre2post & idmap_post2pre
  for (size_t g = 0; g < idmap_post2pre.size(); g++) {
    const PrimitiveGate& preGat = pre_level_optnodes[idmap_post2pre[g]];
    GType type = preGat.type();
    int pre_gid = preGat.gid();
    int lev = levels[idmap_post2pre[g]];
    std::vector<int> preds_in;
    for (int p = 0; p < preGat.numPreds(); p++) {
      preds_in.push_back(idmap_pre2post[preGat.preds()[p]]);
    }
    std::vector<int> succs_in;
    for (int s = 0; s < preGat.numSuccs(); s++) {
      succs_in.push_back(idmap_pre2post[preGat.succs()[s]]);
    }
    PrimitiveGate postGat(pre_gid, lev, type, preds_in, succs_in);
    postGat.gidStd(preGat.gidStd());
    postGat.netName(preGat.netName());
    postGat.stdCellInstName(preGat.stdCellInstName());
    post_level_optnodes.push_back(postGat);
    // Populate levels that correspond to index of post opt netlist
    levels_post_opt.push_back(levels[idmap_post2pre[g]]);
  }
  return STATUS_OK;
}

/**
 * @brief: Populate optimized nodes for simulation.
 * @return status
 */
int NetlistManager::populateOptNodesForSimulation(const std::vector<PrimitiveGate>& post_level_optnodes,
                                                  const std::vector<int>& levels_post_opt)
{
  for (size_t n = 0; n < post_level_optnodes.size(); n++) {
    const PrimitiveGate& rg = post_level_optnodes[n];
    int std_cell_id = rg.gidStd();
    if (std_cell_id != -1) {
      stdCellNetlist().stdCellandIONode(std_cell_id).addStd2Sim(n);  // update std_cell_gid -> sim_gid
    }

    SimGate gat;
    gat.gid(n);
    gat.type(rg.type());
    gat.level(levels_post_opt[n]);
    gat.preds(rg.preds());
    gat.predsC0(rg.preds());
    gat.predsC1(rg.preds());
    gat.succs(rg.succs());
    gat.gidPri(rg.gid());     // map sim_gid -> pri_gid
    gat.gidStd(std_cell_id);  // map sim_gid -> std_cell_id
    gat.netName(rg.netName());
    gat.stdInstName(rg.stdCellInstName());
    primitiveNetlist().updateGidSim(rg.gid(), n);  // update pri_gid -> sim_gid
    simNetlist().appendGate(gat);
    // simNetlist().netNameAndSimid(gat.netName(), n);
  }

  if (simNetlist().computeCircuitStatistics()) {
    return STATUS_ERROR;
  }
  // simNetlist().populateNetlistVec();

  return STATUS_OK;
}
}  // namespace ifsim